﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using HydraPackageTool;
using Microsoft.VisualStudio.Shell;
using System.ComponentModel;
using System.Runtime.InteropServices;
using EnvDTE;
using VisualStudioProvider;
using System.IO;
using HydraPackageTool.Generators;
using HydraPackageToolImp.Templates;
using Metaspec;
using System.Diagnostics;
using Utils;

namespace HydraPackageToolImp
{
    public class PackageTool : IHydraPackageTool
    {
        private object site;
        private DTE dte;
        private ServiceProvider serviceProvider;
        private VSSolution solution;
        private string solutionName;

        public int DefaultExtension(out string pbstrDefaultExtension)
        {
            pbstrDefaultExtension = ".client.cs";
            return 0;
        }

        public int Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, IntPtr[] rgbOutputFileContents, out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
        {
            solution = new VSSolution(solutionName);

            var thisProject = solution.Projects.Single(p => p.Items.Any(i => i.FilePath == wszInputFilePath));
            var clientProject = solution.Projects.Where(p => p.ProjectType == "KnownToBeMSBuildFormat").Single(p =>
            {
                var iterator = p.Select(string.Format("{0}:Project/{0}:PropertyGroup/{0}:PackageServerProject", p.XPathNamespacePrefix));

                if (iterator.Count > 0)
                {
                    iterator.MoveNext();

                    var value = iterator.Current.Value;
                    var fileInfo = new FileInfo(p.FileName);
                    var path = Path.GetFullPath(Path.Combine(fileInfo.DirectoryName, value));

                    return path == thisProject.FileName;
                }
                else
                {
                    return false;
                }
            });

            var project = ICsProjectFactory.create(project_namespace.pn_project_namespace);
            var snippet = ICsSnippetFactory.create(bstrInputFileContents.ToCharArray(), null);

            project.parseSnippet(snippet, CsExpectedSnippet.cses_all, null, true);

            var node = snippet.getNodes().FirstOrDefault();
            Debug.Assert(node.e == cs_node.n_compilation_unit);

            var host = new TemplateEngineHost();

            host.ResolveType += (sender, args) =>
            {
                var iproject = (ICsProject)((VSProject)thisProject).IProject;

                var namespaces = args.Namespaces;
                var files = iproject.getFiles().Where(f => 
                {
                    var path = f.getPath();
                    var buffer = File.ReadAllText(path).ToCharArray();

                    snippet = ICsSnippetFactory.create(buffer, null);

                    project.parseSnippet(snippet, CsExpectedSnippet.cses_all, null, true);

                    var compilationUnit = (CsCompilationUnit) snippet.getNodes().FirstOrDefault();

                    return compilationUnit != null && compilationUnit.declarations != null && compilationUnit.declarations.OfType<CsNamespace>().Any(n =>
                    {
                        var namespaceText = string.Empty;

                        n.qualified_identifier.ToList().ForEach(i => namespaceText += i.identifier.identifier + ".");
                        namespaceText = namespaceText.RemoveEnd(1);

                        return args.Namespaces.Any(n2 => n2 == namespaceText);

                    });
                });

                var count = files.Count();
            };

            var data = host.GenerateFile<PackageGeneratorTemplate>(wszDefaultNamespace, node);

            // return our data, so that Visual Studio may write it to disk.
            var fileContents = Marshal.AllocCoTaskMem(data.Length);

            Marshal.Copy(data, 0, fileContents, data.Length);

            pcbOutput = (uint) data.Length;
            
            return 0;
        }

        public void GetSite(ref Guid riid, out IntPtr ppvSite)
        {
            if (this.site == null)
            {
                throw new Win32Exception(-2147467259);
            }

            var objectPointer = Marshal.GetIUnknownForObject(this.site);

            try
            {
                Marshal.QueryInterface(objectPointer, ref riid, out ppvSite);

                if (ppvSite == IntPtr.Zero)
                {
                    throw new Win32Exception(-2147467262);
                }
            }
            finally
            {
                if (objectPointer != IntPtr.Zero)
                {
                    Marshal.Release(objectPointer);
                    objectPointer = IntPtr.Zero;
                }
            }
        }

        public void SetSite(object pUnkSite)
        {
            this.site = pUnkSite;
        }

        public object DTE
        {
            set 
            {
                dte = (EnvDTE.DTE) value;
                solutionName = dte.Solution.FullName;
            }
        }

        ServiceProvider IHydraPackageTool.SiteServiceProvider
        {
            set 
            {
                this.serviceProvider = value;
            }
        }

        public string SolutionName
        {
            set
            {
                solutionName = value;
            }
        }
    }
}
